在 Android P 以前,一些恶意 App(更多是我们自己写的屎山代码),会短时间频繁向系统进程发起 binder 回调注册。
当客户端向服务端发起回调注册时,服务端会创建 BinderProxy 对象(Java层)与 BpBinder 对象(Native层),短时间过多的对象创建,会使得内存耗尽,导致卡顿死机等问题。
那么解决的办法就是限制对象的创建,前面我们分析过了 BinderProxy 是 native 层 BpBinder 的马甲,BinderProxy 只是一套 Java 层接口,核心功能均通过 BpBinder 提供,所以我们限制 BpBinder 对象的创建即可。
这个 Bug 在 Android P 就解决了,我们使用 Android Q 的代码来看看是怎么解决的:
BpBinder* BpBinder::create(int32_t handle) {
int32_t trackedUid = -1;
if (sCountByUidEnabled) { //sCountByUidEnabled 标志变量为 true,才对 BpBinder 的创建过程做限制
//拿到客户端的 Uid
trackedUid = IPCThreadState::self()->getCallingUid();
AutoMutex _l(sTrackingLock);
//sTrackingMap 类型是 std::unordered_map<int32_t,uint32_t>
//保存了每个 UID 创建的 BpBinder 数量
uint32_t trackedValue = sTrackingMap[trackedUid];
if (CC_UNLIKELY(trackedValue & LIMIT_REACHED_MASK)) { //没有超过 BpBinder 数量限制
if (sBinderProxyThrottleCreate) { // false ,不进入
return nullptr;
}
} else { //超过 BpBinder 数量限制
if ((trackedValue & COUNTING_VALUE_MASK) >= sBinderProxyCountHighWatermark) {
ALOGE("Too many binder proxy objects sent to uid %d from uid %d (%d proxies held)",
getuid(), trackedUid, trackedValue);
sTrackingMap[trackedUid] |= LIMIT_REACHED_MASK;
//调用注册的函数指针回调处理过多的 BpBinder 创建
if (sLimitCallback) sLimitCallback(trackedUid);
if (sBinderProxyThrottleCreate) {
ALOGI("Throttling binder proxy creates from uid %d in uid %d until binder proxy"
" count drops below %d",
trackedUid, getuid(), sBinderProxyCountLowWatermark);
return nullptr;
}
}
}
//记录加1
sTrackingMap[trackedUid]++;
}
//创建新的
return new BpBinder(handle, trackedUid);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
可以看出整个流程还是比较清晰简单的:
- 用一个 map 来保存每个 uid 创建的 BpBinder 数量
- 每次创建 BpBinder 的时候,从 map 中找出客户端 uid 创建的 BpBinder 数量
- 如果操作限制了,打印 Log,并调用 sLimitCallback 函数指针回调来处理
BpBinder 把处理的工作通过回调的方式扔给了 Binder 服务所在的进程,我们以 AMS 为例,来看看注册的回调是怎样的:
BinderInternal.setBinderProxyCountCallback(
new BinderInternal.BinderProxyLimitListener() {
@Override
public void onLimitReached(int uid) {
Slog.wtf(TAG, "Uid " + uid + " sent too many Binders to uid "
+ Process.myUid());
BinderProxy.dumpProxyDebugInfo();
if (uid == Process.SYSTEM_UID) {
Slog.i(TAG, "Skipping kill (uid is SYSTEM)");
} else {
killUid(UserHandle.getAppId(uid), UserHandle.getUserId(uid),
"Too many Binders sent to SYSTEM");
}
}
}, mHandler);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
AMS 的做法简单粗暴有效:
- 打印错误信息
- 如果客户端不是系统进程,直接杀掉